package io.mft.commons.db.mapper;

import java.io.BufferedReader;
import java.io.Reader;
import java.sql.Clob;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.DaoTemplate;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.Page;
import cn.hutool.db.PageResult;
import cn.hutool.db.dialect.DialectName;
import io.mft.commons.db.source.DbProvider;
import lombok.extern.slf4j.Slf4j;
/**
 * 单表CRUD操作
 * @说明：TODO
 * @类型名称：TableDbMapper
 * @创建者: 胡常建  
 * @创建时间: 2020年9月4日 上午10:48:51
 * @修改者: 胡常建  
 * @修改时间: 2020年9月4日 上午10:48:51
 */
@Slf4j
public class TableDbMapper<K> implements DbMapper<K> {

	Logger logger=LoggerFactory.getLogger(getClass());
	private static String DEFAULT_TABLE="default";
	private DaoTemplate daoTemplate;
	private Class<K> kClass;
	private String datasourceId;
	private DialectName dialectName;
	private String tbName;
	private TableDbMapper(String datasourceId,String tbName,String primaryKeyField,Class<K> kClass) {
		this.kClass=kClass;
		daoTemplate=new DaoTemplate(tbName,primaryKeyField, DbProvider.getInstance().getDataSource(datasourceId));
		this.datasourceId=datasourceId;
		Db db=DbProvider.getInstance().getDb(datasourceId);
		dialectName = DialectName.valueOf(db.getRunner().getDialect().dialectName());
		this.tbName=tbName;
	}
	public final static Set<String> PARTICULAR_COLUMN=new HashSet<>();
	static {
		PARTICULAR_COLUMN.add("order_by");
		PARTICULAR_COLUMN.add("is_asc");
		PARTICULAR_COLUMN.add("order_by_column");
		PARTICULAR_COLUMN.add("page_no");
		PARTICULAR_COLUMN.add("page_num");
		PARTICULAR_COLUMN.add("page_size");
		PARTICULAR_COLUMN.add("__body-handled");
		PARTICULAR_COLUMN.add("__limit");
		PARTICULAR_COLUMN.add("__page");
		PARTICULAR_COLUMN.add("__order");
		PARTICULAR_COLUMN.add("pageNo");
		PARTICULAR_COLUMN.add("pageSize");
	}
	private static Map<String, Object> cache=new HashMap<>();
	public  static <K> TableDbMapper<K> getInstance(String datasourceId,Class<K> kClass){
		return getInstance(datasourceId, DEFAULT_TABLE, kClass);
	}
	public  static <K> TableDbMapper<K> getInstance(String datasourceId,String tbName,Class<K> kClass) {
		return getInstance(datasourceId, tbName, "id", kClass);

	}
	@SuppressWarnings("unchecked")
	public  static <K> TableDbMapper<K> getInstance(String datasourceId,String tbName,String primaryKeyField,Class<K> kClass){
		String key=datasourceId+"."+tbName+"."+primaryKeyField+"."+kClass.getName();
		Object object=cache.get(key);
		if (object!=null) {
			TableDbMapper<K> mapper=(TableDbMapper<K>)object;
			return mapper;
		}else {
			synchronized (cache) {
				Object t=cache.get(key);
				if (t!=null) {
					TableDbMapper<K> mapper=(TableDbMapper<K>)t;
					return mapper;
				}else {
					TableDbMapper<K> mapper=new TableDbMapper<>(datasourceId,tbName,primaryKeyField,kClass);
					cache.put(key, mapper);
					return mapper;
				}
			}
		}
	}
	private void removeNullOrEmpty(Entity event) {
		Iterator<Entry<String, Object>> iterator=event.entrySet().iterator();
		while (iterator.hasNext()) {
			Map.Entry<java.lang.String, java.lang.Object> entry = (Map.Entry<java.lang.String, java.lang.Object>) iterator
					.next();
			if (entry.getValue()==null||StrUtil.isEmpty(String.valueOf(entry.getValue()))) {
				iterator.remove();
			}
		}
	}
	@SuppressWarnings("unchecked")
	private Entity parseBeanToEntity(K bean) {
		Entity entity;
		if (bean instanceof Map) {
			entity=new Entity();
			entity.putAll((Map<? extends String, ? extends Object>)bean);
		}else {
			Map<String, Object> map=BeanUtil.beanToMap(bean, true, false);
			entity=new Entity();
			Iterator<Entry<String, Object>> it=map.entrySet().iterator();
			while (it.hasNext()) {
				Entry<String, Object> entry =  it.next();
				entity.put(StrUtil.toUnderlineCase(entry.getKey()), entry.getValue());
			}
		}
		for (String key : PARTICULAR_COLUMN) {
			entity.remove(key);
		}
		return entity;
	}
	@Override
	public int add(K bean) throws SQLException {
		Entity entity=parseBeanToEntity(bean);
		return daoTemplate.add(entity);
	}

	@Override
	public List<Object> addForGeneratedKeys(K bean) throws SQLException {
		Long id=addForGeneratedKey(bean);
		List<Object> ls=Arrays.asList(id);
		return ls;
	}

	@Override
	public Long addForGeneratedKey(K bean) throws SQLException {
		Entity entity=parseBeanToEntity(bean);
		if (dialectName==DialectName.ORACLE) {
			String id=io.mft.commons.db.DbUtil.oraclePkId(datasourceId, this.tbName);
			entity.set("id", id);
			daoTemplate.add(entity);
			return Long.valueOf(id);
		}else {
			return daoTemplate.addForGeneratedKey(entity);
		}
	}

	@Override
	public <T> int delBypk(T pk) throws SQLException {
		return daoTemplate.del(pk);
	}

	@Override
	public <T> int del(String field, T value) throws SQLException {
		//String uf=StrUtil.toUnderlineCase(field);
		return daoTemplate.del(field, value);
	}

	@Override
	public int del(K where,boolean removeWhereNullOrEmpty) throws SQLException {
		Entity wentity=parseBeanToEntity(where);
		if (removeWhereNullOrEmpty) {
			removeNullOrEmpty(wentity);
		}
		return daoTemplate.del(wentity);
	}

	@Override
	public int update(K record, K where,boolean removeWhereNullOrEmpty,boolean removeRecordNullOrEmpty) throws SQLException {
		Entity wentity=parseBeanToEntity(where);
		if (removeWhereNullOrEmpty) {
			removeNullOrEmpty(wentity);
		}
		Entity rentity=parseBeanToEntity(record);
		if (removeRecordNullOrEmpty) {
			removeNullOrEmpty(rentity);
		}
		return daoTemplate.update(rentity, wentity);
	}

	@Override
	public int update(K bean,boolean removeNullOrEmpty) throws SQLException {
		Entity entity=parseBeanToEntity(bean);
		if (removeNullOrEmpty) {
			removeNullOrEmpty(entity);
		}
		return daoTemplate.update(entity);
	}

	@Override
	public int addOrUpdate(K bean,boolean removeNullOrEmpty) throws SQLException {
		Entity entity=parseBeanToEntity(bean);
		if (removeNullOrEmpty) {
			removeNullOrEmpty(entity);
		}
		return daoTemplate.addOrUpdate(entity);
	}

	@Override
	public <T> K getBypk(T pk) throws SQLException {
		Entity entity= daoTemplate.get(pk);
		return parseEntityToBean(entity,kClass);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static<K> K parseEntityToBean(Entity entity,Class<K> kClass) {
		if (entity==null) {
			return null;
		}
		Set<String> keys=entity.keySet();
		Map<String, Object> map=new HashMap<>();
		for (String key : keys) {
			 map.put(key.toLowerCase(),entity.getObj(key));
		}
		K k=ReflectUtil.newInstance(kClass);
		if (k instanceof Map<?, ?>) {
			((Map) k).putAll(map);
		}else {
			BeanUtil.fillBeanWithMap(map, k, true, true);
		}
		return k;
	}
	public static <K> List<K> parseEntityToBean(List<Entity> entities,Class<K> kClass) {
		if (entities==null) {
			return null;
		}
		List<K> ks=new ArrayList<K>();
		for (Entity entity : entities) {
			 K k=parseEntityToBean(entity,kClass);
			 ks.add(k);
		}
		return ks;
	}
	@Override
	public <T> K get(String field, T value) throws SQLException {
		//String uf=StrUtil.toUnderlineCase(field);
		Entity entity=daoTemplate.get(field, value);
		parseValue(entity);
		return parseEntityToBean(entity,kClass);
	}

	@Override
	public K get(K where,boolean removeWhereNullOrEmpty) throws SQLException {
		Entity ewhere=parseBeanToEntity(where);
		if (removeWhereNullOrEmpty) {
			removeNullOrEmpty(ewhere);
		}
		Entity entity= daoTemplate.get(ewhere);
		parseValue(entity);
		return parseEntityToBean(entity,kClass);
	}

	@Override
	public <T> List<K> find(String field, T value) throws SQLException {
		//String uf=StrUtil.toUnderlineCase(field);
		List<Entity> entities=daoTemplate.find(field, value);
		parseValue(entities);
		return parseEntityToBean(entities,kClass);
	}

	@Override
	public List<K> findAll() throws SQLException {
		List<Entity> entities=daoTemplate.findAll();
		parseValue(entities);
		return parseEntityToBean(entities,kClass);
	}

	@Override
	public List<K> find(K where,boolean removeWhereNullOrEmpty) throws SQLException {
		Entity ewhere=parseBeanToEntity(where);
		if (removeWhereNullOrEmpty) {
			removeNullOrEmpty(ewhere);
		}
		List<Entity> entities=daoTemplate.find(ewhere);
		parseValue(entities);
		return parseEntityToBean(entities,kClass);
	}


	@Override
	public PageResult<K> page(K where, Page page,boolean removeWhereNullOrEmpty, String... selectFields) throws SQLException {
		String dbType=DbProvider.getInstance().dbType(datasourceId);
		if (dbType.equals(DialectName.MYSQL.name())) {
			page.setPageNumber(page.getPageNumber()-1);
		}
		Entity ewhere=parseBeanToEntity(where);
		if (removeWhereNullOrEmpty) {
			removeNullOrEmpty(ewhere);
		}
		PageResult<Entity> pageResult=daoTemplate.page(ewhere, page, selectFields);
		parseValue(pageResult);
		PageResult<K> kPageResult=new PageResult<>(pageResult.getPage(), pageResult.getPageSize(), pageResult.getTotal());
		List<K> ks=parseEntityToBean(pageResult,kClass);
		kPageResult.addAll(ks);
		return kPageResult;
	}

	public void parseClob(Entity entity) {
		
	}
	public static void parseValue(List<Entity> entities) {
		for (Entity entity : entities) {
			parseValue(entity);
		}
	}
    public static String clobToString(Clob clob) {
    	if (ObjectUtil.isNull(clob)) {
			return "";
		}
        String reString = "";
        Reader is=null;
        BufferedReader br=null;
        try{
            is = clob.getCharacterStream();
            br = new BufferedReader(is);
            String s = br.readLine();
            StringBuffer sb = new StringBuffer();
            while (s != null) {
                sb.append(s);
                s = br.readLine();
            }
            reString = sb.toString();
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }finally {
            if(br!=null){
                try{
                    br.close();
                }catch (Exception e){
                    log.error(e.getMessage(),e);
                }
            }
            if(is!=null){
                try{
                    is.close();
                }catch (Exception e){
                    log.error(e.getMessage(),e);
                }
            }
        }
        return reString;
    }
	public static void parseValue(Entity entity) {
		if (ObjectUtil.isNull(entity)) {
			return;
		}
		List<String> fs=new ArrayList<>(entity.keySet());
		for (String k : fs) {
			 Object obj= entity.get(k);
			 if (ObjectUtil.isNull(obj)) {
				continue;
			}
			 String key="str_"+k;
			 if (obj instanceof Date) {
				 entity.put(key, DateUtil.format((Date)obj, "yyyy-MM-dd HH:mm:ss"));
				 entity.addFieldNames(key);
			}else if (obj instanceof Timestamp) {
				 Timestamp tt=(Timestamp)obj;
				 entity.put(key, DateUtil.format(new Date(tt.getTime()), "yyyy-MM-dd HH:mm:ss"));
				 entity.addFieldNames(key);
			}else if (obj instanceof Clob) {
				Clob clob=(Clob)obj;
				String v=clobToString(clob);
				entity.put(k, v);
			}
		}
	}
	@Override
	public PageResult<K> page(K where, Page page,boolean removeWhereNullOrEmpty) throws SQLException {
		String dbType=DbProvider.getInstance().dbType(datasourceId);
		if (dbType.equals(DialectName.MYSQL.name())) {
			page.setPageNumber(page.getPageNumber()-1);
		}
		Entity ewhere=parseBeanToEntity(where);
		if (removeWhereNullOrEmpty) {
			removeNullOrEmpty(ewhere);
		}
		PageResult<Entity> pageResult=daoTemplate.page(ewhere, page);
		parseValue(pageResult);
		PageResult<K> kPageResult=new PageResult<>(pageResult.getPage(), pageResult.getPageSize(), pageResult.getTotal());
		List<K> ks=parseEntityToBean(pageResult,kClass);
		kPageResult.addAll(ks);
		return kPageResult;
	}

	@Override
	public int count(K where,boolean removeWhereNullOrEmpty) throws SQLException {
		Entity ewhere=parseBeanToEntity(where);
		if (removeWhereNullOrEmpty) {
			removeNullOrEmpty(ewhere);
		}
		Long c= daoTemplate.count(ewhere);
		return c.intValue();
	}

	@Override
	public boolean exist(K where,boolean removeWhereNullOrEmpty) throws SQLException {
		Entity ewhere=parseBeanToEntity(where);
		if (removeWhereNullOrEmpty) {
			removeNullOrEmpty(ewhere);
		}
		return daoTemplate.exist(ewhere);
	}
}
